<?php

defined('__MAPEXEC__') or die('No direct access allowed');

/**
 * Abstract class for exporting maps as HTML.
 */
abstract class Maps_Export_Html extends Maps_Export_Text {
  
  private $apiKey = null;
  private $apiURL = null;
  private $apiVersion = null;
  protected $emptyTags = array('img','br','input','option','param','link','hr','frame','base');
  protected $height = 500;
  protected $libraryRendered = false;
  protected $mapHTMLClass = 'map';
  protected $unit = 'px';
  protected $width = 500;
  
  /**
   * Class constructor.
   * @param   string  Optional API key
   * @return  void
   */
  public function __construct($apiKey='') {
    $this->setAPIKey($apiKey);
  }
  
  /**
   * Closes an HTML <script> tag
   * @param   Boolean   True to ecapsulate script source code in HTML comment.
   * @param   Boolean   True if markup is XML compliant (XHTML)
   * @return  String    Closing </script> tag
   */
  protected function closeScript($includeComment=false, $isXML=false) {
    $output = '';
    
  	if ($includeComment) {
      if ($isXML) {
        $output .= "/*]]>*/\n";
      } else {
        $output .= "-->\n";
      }
    }
    
    $output .= '</script>';
    return $output;
  }
  
  /**
   * Closes an HTML tag
   * @param   String  HTML tag to close
   * @return  String  Closed HTML tag
   */
  protected function closeTag($tagName) {
  	if ( !is_string($tagName) || $this->isEmptyTag($tagName) ) {
  		return '';
  	} else {
  		return '</'.$tagName.'>';
  	}
  }
  
  /**
   * Gets the JavaScript API key for this map.
   * @param   void
   * @return  String  API key
   */
  public function getAPIKey() {
  	return $this->apiKey;
  }
  
  /**
   * Gets the map API URL with the API key and version.
   * @param   void
   * @return  string  API URL with version and API key
   */
  public function getAPIURL() {
    $key = $this->getAPIKey();
    $version = $this->getAPIVersion();
    $url = $this->apiURL;
    
    if ( !empty($key) && !empty($version) && !empty($url) ) {
      return sprintf($url, $version, $key);
    } else {
      echo '<!-- API URL failed: $key: "'.$key.'" | $version: "'.$version.'" | $url: "'.$url.'" -->' . "\n\n";
      return '';
    }
  }
  
  /**
   * Gets the API version in use.
   * @param   void
   * @return  mixed   API version as number or string
   */
  public function getAPIVersion() {
    return $this->apiVersion;
  }
  
  /**
   * Gets the height of the displayed map.
   * @param   void
   * @return  Integer   Height of the map
   */
  protected function getHeight() {
    return $this->height;
  }
  
  /**
   * Gets the maximum width of a map info window overlay.
   * @param   void
   * @return  Integer   Maximum width
   */
  protected function getMaxInfoWindowWidth() {
    return round($this->getWidth() * .75);
  }
  
  /**
   * Gets the unit of measure for map dimensions
   * @param   void
   * @return  String  Unit of measure abbreviation, e.g. px, %, etc.
   */
  public function getUnit() {
    return $this->unit;
  }
  
  /**
   * Gets the displayed map's width.
   * @param   void
   * @return  Integer   Map width
   */
  protected function getWidth() {
    return $this->width;
  }
  
  /**
   * Shortcut function for <img> tag.
   * @param   String  Image source URL path
   * @param   Array   Optional key=>value array of HTML tag attributes and values
   * @return  String  Image HTML tag
   */
  protected function image($src, $attrs=array()) {
    $attrs['src'] = $src;
    return $this->openTag('img', $attrs);
  }
  
  /**
   * Determines if the tag name is an empty element in HTML, not
   * requiring a closing tag.
   * @param   String  HTML tag name
   * @return  Boolean True if an empty element, false otherwise.
   */
  protected function isEmptyTag($tagName) {
  	return in_array($tagName, $this->emptyTags);
  }
  
  /**
   * Shortcut function for creating an opening HTML <script> tag
   * @param   String  Optional URL to an external script file
   * @param   Boolean When true, encapsulates script source code in HTML comment
   * @param   Boolean True if markup is XML compliant (XHTML)
   */
  protected function openScript($url='', $includeComment=false, $isXML=false) {
    $output = '<script type="text/javascript"';
    
  	if ( !empty($url) ) {
  		$output .= ' src="'.$url.'"';
  	}
    
    $output .= '>';
    
    if ($includeComment) {
    	if ($isXML) {
    		$output .= "/*<![CDATA[*/\n";
    	} else {
    		$output .= "<!--\n";
    	}
    }
    
    return $output;
  }
  
  /**
   * Opens an HTML tag.
   * @param   String  HTML tag name
   * @param   Array   Optional key=value array of attributes and values
   * @return  String  Opening HTML tag
   */
  protected function openTag($tagName, $attrs=null) {
    if ( !is_string($tagName) || empty($tagName) ) {
      return '';
    }
    
    $tag = '<'.$tagName;
    
    if ( !empty($attrs) ) {
      foreach ($attrs as $name => $value) {
        $tag .= ' '.$name.'="'.$value.'"';
      }
    }
    
    $tag .= '>';
    return $tag;
  }
  
  /**
   * Renders the various map pieces in HTML
   * @param   Maps_Map_Common   Map object to be exported
   * @param   Maps_Icon_Manager_Common Optional icon manager object
   * @return  Array             Array of HTML strings
   *    invocation  Basic map data
   *    legend      Map legend
   *    library     JavaScript library that displays the map
   *    map         HTML for the actual map displayed to the user
   *    sidebar     Sidebar listing links to all the map Points of Interest
   */
  public function render(Maps_Map_Common $map, Maps_Icon_Manager_Common $iconManager=null) {
    $output['invocation'] = $this->renderInvocationCode($map);
    $output['legend']     = $this->renderLegendCode($map);
    $output['library']    = $this->renderLibraryCode($iconManager);
    $output['map']        = $this->renderMapCode($map);
    $output['sidebar']    = $this->renderSidebarCode($map);
    return $output;
  }
  
  /**
   * Renders the HTML code necessary to import the API to the web page.
   * @param   Maps_Icon_Manager_Common   Option icon manager object
   * @return  string              HTML code
   */
  protected function renderLibraryCode(Maps_Icon_Manager_Common $iconManager=null) {
    $output = '';
    
    if ( !$this->libraryRendered ) {
      $this->libraryRendered = true;
      $src = $this->getAPIURL();
      $output = $this->writeLn( $this->openScript($src) . $this->closeScript() );
    }
    
    if ( !empty($iconManager) ) {
      if ( $iconManager->canExport() ) {
        $iconManager->disableExport();
        $output .=  $this->writeLn( $this->openScript() ) . $this->renderIconCode($iconManager) . $this->outdentLn( $this->closeScript() );
      }
    }
    
    return $output;
  }
  
  /**
   * Renders the map legend in HTML
   * @param   Maps_Map_Common   Map object
   * @return  String            Map legend HTML
   */
  private function renderLegendCode(Maps_Map_Common $map) {
    $legend = $map->legend;
    
    if ( !isset($legend) ) {
      return '';
    }
    
    $item = null;
    $output = $this->writeLn('<dl class="mapLegend" id="'.$map->getId().'_legend">');
    $this->indent();
    while ($item = $legend->next()) {
      $output .= $this->writeLn('<dt>'.$this->image($item['image'], array('alt'=>'')).'</dt>')
              . $this->writeLn('<dd>'.$item['description'].'</dd>');
    }
    $this->outdent();
    $output .= $this->writeLn('</dl>');
    return $output;
  }
  
  /**
   * Renders the HTML code for the map.
   * @param   Maps_Map_Common   Map object
   * @return  string            HTML code
   */
  protected function renderMapCode(Maps_Map_Common $map) {
  	$output = $this->writeLn("<div class='{$this->mapHTMLClass}' id='{$map->getId()}' style='height: {$this->getHeight()}{$this->getUnit()}; width: {$this->getWidth()}{$this->getUnit()};'></div>");
    return $output;
  }
  
  /**
   * Creates the HTML for the address.
   * @param   string  Address text
   * @return  string  Address HTML
   */
  protected function renderPoiAddress($address) {
    $output = '';
    if ( !empty($address) ) {
      $output .= "<address class='mapPoiAddress'>$address</address>";
    }
    return $output;
  }
  
  /**
   * Creates the HTML for the contacts.
   * @param   array   Array of contact information
   * @return  string  Contacts HTML
   */
  protected function renderPoiContacts(array $contacts) {
    $output = '';
    if ( !empty($contacts) ) {
      $output .= '<ul class="mapPoiContacts"><li>' . implode('</li><li>',$contacts) . '</li></ul>';
    }
    return $output;
  }
  
  /**
   * Creates the description text.
   * @param   string  Description text. HTML can be used.
   * @param   string  URL to the "more info" page for this point of interest on this site
   * @param   string  "more info" link text at the end of the description.
   * @return  string  Description HTML
   */
  protected function renderPoiDescription($description, $url, $urlText) {
    $output = '';
    if ( !empty($description) ) {
      $output .= '<div class="mapPoiDescription">'.$description;
      if ( !empty($url) ) {
        $output .= " <a href='$url'>$urlText</a>";
      }
      $output .= '</div>';
    }
    return $output;
  }
  
  /**
   * Creates the HTML for the point of interest image.
   * @param   string  URL to an image.
   * @param   integer Width of image in pixels
   * @param   integer Height of image in pixels
   * @param   string  URL to the "more info" page for this point of interest on this site
   * @return  string  Image HTML
   */
  protected function renderPoiImage($image, $width, $height, $url) {
    $output = '';
    $imageOutput = '';
    if ( !empty($image) ) {
      $imageOutput = $this->image($image, array( 'width'=>$width, 'height'=>$height ));
      if ( empty($url) ) {
        $output .= "<div class='mapPoiImage'>$imageOutput</div>";
      } else {
        $output .= "<div class='mapPoiImage'><a href='$url'>$imageOutput</a></div>";
      }
    }
    return $output;
  }
  
  /**
   * Creates the HTML for this point of interest's info window.
   * @param   Maps_Map_POI      Point of Interest object
   * @param   Maps_Map_Common   Map object
   * @return  string            HTML text inserted by JavaScript into POI info window.          
   */
  protected function renderPoiInfo(Maps_Map_POI $poi, Maps_Map_Common $map) {
    $image = $poi->getImage();
    $description = $poi->getDescription();
    $address = $poi->getAddress();
    $contacts = $poi->getContacts();
    $mapId = $map->getId();
    $name = $poi->getName();
    $url = $poi->getURL();
    $website = $poi->getWebsite();
    $title = $poi->getTitle();
    
    $output = '';
    $headingLevel = 1;
    
    if ( !empty($title) ) {
      $output .= $this->renderPoiTitle($title, $headingLevel);
      $headingLevel++;
    }
    
    $output .=  $this->renderPoiName($name, $url, $headingLevel) .
                $this->renderPoiImage($image, $poi->getImageWidth(), $poi->getImageHeight(), $url) .
                $this->renderPoiDescription($description, $url, $poi->getUrlText()) .
                $this->renderPoiAddress($address) .
                $this->renderPoiContacts($contacts) .
                $this->renderPoiWebsite($website, $poi->getWebsiteText()) .
                $this->renderPoiLinks($poi, $map);
    
    return $output;
  }
  
  /**
   * Creates the HTML for the Point of Interest's name
   * @param   string  Name text
   * @param   string  URL to the "more info" page for this point of interest on this site
   * @param   integer HTML tag heading level: 1 - 6
   * @return  string  Name HTML
   */
  protected function renderPoiName($name, $url, $headingLevel=1) {
    return empty($url) ?
        "<h$headingLevel class='mapPoiName'>$name</h$headingLevel>" :
        "<h$headingLevel class='mapPoiName'><a href='$url'>$name</a></h$headingLevel>";
  }
  
  /**
   * Creates the title text for this point of interest.
   * @param   string  Title text
   * @return  string  Title HTML
   */
  protected function renderPoiTitle($title, $headingLevel) {
    return "<h$headingLevel class='mapPoiTitle'>$title</h$headingLevel>";
  }
  
  /**
   * Creates the external web site link for this point of interest.
   * @param   string  URL to an external web site
   * @param   string  Web site link text
   * @return  string  Link HTML
   */
  protected function renderPoiWebsite($website, $websiteText) {
    $output = '';
    if ( !empty($website) ) {
      $output .= "<p class='mapPoiWebsite'><a href='$website' target='_blank'>$websiteText</a></p>";
    }
    return $output;
  }
  
  /**
   * Renders the map side bar.
   * @param   array   Array of Point of Interest objects
   * @return  string  HTML for map side bar
   */
  private function renderSidebarCode(Maps_Map_Common $map) {
    $output = '';
    if ($map->pois->getCount() > 0) {
      $output = $this->writeLn('<ul class="mapSidebar" id="'.$map->getId().'_sidebar">');
      
      $this->indent();
      while ($poi = $map->pois->next()) {
        $output .= $this->writeLn('<li><a href="#'.$map->getId().'">'.$poi->getName().'</a></li>');
      }
      
      $this->outdent();
      $output .= $this->writeLn('</ul>');
    }
    
    return $output;
  }
  
  /**
   * Sets the JavaScript API key used to export this map
   * @param   String  API key
   * @return  void
   */
  public function setAPIKey($key='') {
  	if ( !empty($key) ) {
  		$this->apiKey = $key;
  	}
    if ( empty($this->apiKey) && $this->requiresAPIKey() ) {
      die('This export service requires an API Key. None was given.');
    }
  }
  
  /**
   * Sets the API URL.
   * @param   string  API URL with %s in place of version and API key respectively
   * @return  void
   */
  protected function setAPIURL($url) {
    if ( !empty($url) && is_string($url) ) {
      $this->apiURL = $url;
    }
  }
  
  /**
   * Sets the API version in use.
   * @param   mixed   Version number as a string, float or integer
   * @return  void
   */
  protected function setAPIVersion($version) {
    if ( !empty($version) && (is_string($version) || is_float($version) || is_integer($version) ) ) {
      $this->apiVersion = $version;
    }
  }
  
  /**
   * Sets the displayed map's height
   * @param   Integer   Height of map
   * @return  void
   */
  public function setHeight($int=-1) {
    if ($int > -1) {
      $this->height = $int;
    }
  }
  
  /**
   * Sets the unit of measure for map dimensions
   * @param   Unit of measure abbreviation, e.g. px, %, etc.
   * @return  void
   */
  public function setUnit($str) {
    $this->unit = (string) $str;
  }
  
  /**
   * Sets the displayed map's width
   * @param   Integer   Width of map
   * @return  void
   */
  public function setWidth($int=-1) {
    if ($int > -1) {
      $this->width = $int;
    }
  }
  
  /**
   * Gets the service-specific layer control type
   */
  abstract protected function getLayerControlType($type='default');
  
  /**
   * Gets the service-specific navigation control type
   */
  abstract protected function getNavigationType($type='default');
  
  /**
   * Render the service-specific JavaScript API version.
   */
  abstract protected function renderAPIVersion();
  
  /**
   * Renders the icon code for a specific service
   */
  abstract protected function renderIconCode(Maps_Icon_Manager_Common $iconManager);
  
  /**
   * Renders the service-specific JavaScript that displays the map
   */
  abstract protected function renderInvocationCode(Maps_Map_Common $map);
  
  /**
   * Renders the service-specific code for the point of interest links
   */
  abstract protected function renderPoiLinks(Maps_Map_POI $poi, Maps_Map_Common $map);
  
}
?>